package com.hup.utils.commons.hardware;

import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;
import lombok.extern.slf4j.Slf4j;

import javax.swing.SwingUtilities;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.DisplayMode;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author hugan
 * @date 2020/4/18
 */
@Slf4j
public class ScreenUtil {

    /**
     * 获取整个屏幕的分辨率(包含任务栏的区域)
     */
    public static Dimension getScreenSize() {
        return Toolkit.getDefaultToolkit().getScreenSize();
    }

    /**
     * 获取屏幕内'可用区域(可含任务栏的区域)'的坐标;
     */
    public static Insets getScreenCoor() {
        Dimension screenSize = getScreenSize();
        return new Insets(0, 0, screenSize.height - 1, screenSize.width - 1);
    }

    /**
     * 获取屏幕内'可用区域(不含任务栏的区域)'的坐标;
     * 坐标取值:可用区域的[上/左]的起点,[下/右]的终点,是index,起始于0
     *
     * @apiNote 如无状态栏时, 宽高最大的可用坐标是[宽/高-1],而不是[宽高本身](因为是index)!
     */
    public static Insets getScreenUsableCoor() {
        Insets insets = Toolkit.getDefaultToolkit().getScreenInsets(
                GraphicsEnvironment.getLocalGraphicsEnvironment()
                        .getDefaultScreenDevice().getDefaultConfiguration());
        Dimension screenSize = getScreenSize();
        insets.right = screenSize.width - insets.right - 1;
        insets.bottom = screenSize.height - insets.bottom - 1;
        return insets;
    }

    /**
     * 获取屏幕内'可用区域(不含任务栏的区域)'的 Bounds,类似 {@link Component#getBounds()}
     */
    public static Rectangle getScreenUsableBounds() {
        Insets insets = Toolkit.getDefaultToolkit().getScreenInsets(
                GraphicsEnvironment.getLocalGraphicsEnvironment()
                        .getDefaultScreenDevice().getDefaultConfiguration());
        Dimension screenSize = getScreenSize();
        return new Rectangle(insets.left, insets.top,
                screenSize.width - insets.left - insets.right,
                screenSize.height - insets.top - insets.bottom
        );
    }

    /**
     * @param device nullable-获取默认显示器,当前显示器
     * @return 当前显示模式[分辨率等信息]
     */
    public static DisplayMode getDisplayMode(GraphicsDevice device) {
        if (device == null) device = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
        return device.getDisplayMode();
    }

    /**
     * 获取屏幕DPI
     *
     * @apiNote 未测试多屏幕的情况
     */
    public static int getDpi() {
        return Toolkit.getDefaultToolkit().getScreenResolution();
    }

    /**
     * 获取屏幕缩放比例
     *
     * @return null-未知缩放比例;[100,200]
     * @apiNote 系统改变比例后, 要重启程序才能获取最新的比例;未测试多屏幕的情况
     */
    public static Integer getScreenScale() {
        return getScreenScale(null);
    }

    /**
     * 获取屏幕缩放比例
     *
     * @return 未知缩放比例时, 返回defValue;[100,200]
     * @apiNote 系统改变比例后, 要重启程序才能获取最新的比例;未测试多屏幕的情况
     */
    public static Integer getScreenScale(Integer defValue) {
        //对应表格来自微软官网
        //https://blog.csdn.net/caoshangpa/article/details/73480459
        int dpi = Toolkit.getDefaultToolkit().getScreenResolution();
        switch (dpi) {
            case 96:
                return 100;
            case 120:
                return 125;
            case 144:
                return 150;
            case 168:
                return 175;
            case 192:
                return 200;
            case 216:
                return 225;
            default:
                return defValue;
        }
    }

    /**
     * 用于配置文件中的字段检查
     */
    public static int checkScreenScaleValue(int screenScale) {
        if (screenScale != 100 && screenScale != 125
                && screenScale != 150 && screenScale != 175
                && screenScale != 200) {
            return getScreenScale(100);
        } else {
            return screenScale;
        }
    }

    /**
     * 获取屏幕内'可用区域(可含任务栏的区域)'的坐标;
     *
     * @return minY, minX, maxY, maxX; 注意包含maxX,maxY的问题
     * @apiNote 包含侧边栏;
     * @apiNote 算法关键词:多个矩形的最小包围盒/外包矩形
     */
    public static Insets getAllScreenInsets() {
        int minX = Integer.MAX_VALUE;
        int minY = Integer.MAX_VALUE;
        int maxX = 0;
        int maxY = 0;

        GraphicsEnvironment environment = GraphicsEnvironment.getLocalGraphicsEnvironment();
        for (GraphicsDevice device : environment.getScreenDevices()) {
            GraphicsConfiguration configuration = device.getDefaultConfiguration();

            Rectangle bounds = configuration.getBounds();
            if (bounds.x < minX) minX = bounds.x;
            if (bounds.y < minY) minY = bounds.y;
            if (bounds.x + bounds.width - 1 > maxX) maxX = bounds.x + bounds.width - 1;
            if (bounds.y + bounds.height - 1 > maxY) maxY = bounds.y + bounds.height - 1;
        }
        return new Insets(minY, minX, maxY, maxX);
    }

    public static Rectangle getAllScreenRect() {
        Insets insets = getAllScreenInsets();
        int width = insets.right - insets.left + 1;
        int height = insets.bottom - insets.top + 1;
        return new Rectangle(insets.left, insets.top, width, height);
    }

    public static Dimension getAllScreenSize() {
        Rectangle rect = getAllScreenRect();
        return new Dimension(rect.width, rect.height);
    }

    /**
     * 简陋的显示器切换监听管理类
     */
    @Log4j2
    @NoArgsConstructor(access = AccessLevel.PRIVATE)
    public static class ScreenChangedManager {

        private static class SingletonHolder {
            private static ScreenChangedManager INSTANCE = new ScreenChangedManager();
        }

        public static ScreenChangedManager getInstance() {
            return SingletonHolder.INSTANCE;
        }

        private ExecutorService executor = Executors.newSingleThreadExecutor();
        private boolean listen;

        private ScreenListener screenListener;
        private GraphicsDevice device;
        private DisplayMode lastDisplayMode;

        /**
         * 注册监听器;一个程序只允许注册一个
         *
         * @param listener nutNull
         */
        @SuppressWarnings("Duplicates")
        public void registerScreenListener(ScreenListener listener) {
            if (this.screenListener != null) {
                throw new IllegalArgumentException("一个程序只允许注册一个监听器");
            }
            this.screenListener = listener;
            device = listener.getDevice();
            lastDisplayMode = device.getDisplayMode();
            listen = true;
            executor.execute(() -> {
                Thread.currentThread().setName(ScreenChangedManager.class.getSimpleName());
                log.info("显示器监听线程已启动");
                try {
                    while (listen) {
                        try {
                            Thread.sleep(listener.checkSpan());
                        } catch (InterruptedException e) {
                            log.error("InterruptedException", e);
                            Thread.currentThread().interrupt();
                            return;
                        }
                        DisplayMode cur = device.getDisplayMode();
                        if (!cur.equals(lastDisplayMode)) {
                            Dimension newer = new Dimension(cur.getWidth(), cur.getHeight());
                            Dimension older = new Dimension(lastDisplayMode.getWidth(), lastDisplayMode.getHeight());
                            lastDisplayMode = cur;
                            //不用catch异常
                            SwingUtilities.invokeLater(() -> listener.onScreenChanged(newer, older, cur, lastDisplayMode));
                        }
                    }
                } finally {
                    log.info("显示器监听线程已停止");
                    removeScreenListener();
                }
            });
        }

        public void removeScreenListener() {
            listen = false;
            this.screenListener = null;
        }

        public interface ScreenListener {

            /**
             * @return 显示器, 默认返回默认显示器
             */
            default GraphicsDevice getDevice() {
                return GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
            }

            /**
             * @return 每隔n毫秒轮询一次
             */
            default int checkSpan() {
                return 2000;
            }

            /**
             * 在 AWT线程中通知屏幕改变[分辨率,刷新率等]
             *
             * @apiNote 注意, 系统有可能很频繁/快速的切换分辨率,所以不要在本方法处理耗时任务
             */
            void onScreenChanged(Dimension newer, Dimension older,
                    DisplayMode newerMode, DisplayMode olderMode);
        }
    }

}
